using System;
using System.Xml;
using System.Xml.Serialization;
using System.Globalization;
using System.Security.Permissions;
using GeoFramework;
#if !PocketPC || DesignTime
using System.Runtime.Serialization;
using System.ComponentModel;
#endif

namespace GeoFramework.Gps
{

	/// <summary>Represents a measurement of the strength of a received radio signal.</summary>
	/// <remarks>Signal-to-Noise Ratios (or SNR for short) measure the clarity of
	/// a received signal versus the level at which the signal is being obscured.  For 
	/// example, if a GPS device is operating in open sky on a clear day, no signals are
	/// obscured and the SNR will be high.  Conversely, if a device is activated in a room 
	/// with no view of the sky, signals may be obscured, resulting in a low SNR.  
	/// This class is used frequently by the <see cref="Satellite">Satellite</see>
	/// class to report on the quality of GPS satellite signals.  When three or more 
	/// satellite SNR's are strong, the device is able to obtain a fix on the current location.
	/// </remarks>
#if !PocketPC || DesignTime
#if Framework20
    [TypeConverter("GeoFramework.Design.SignalToNoiseRatioConverter, GeoFramework.Gps.Design, Culture=neutral, Version=2.0.0.0, PublicKeyToken=3ed3cdf4fdda3400")]
#elif Framework20
    [TypeConverter("GeoFramework.Design.SignalToNoiseRatioConverter, GeoFramework.Gps.Design, Culture=neutral, Version=2.0.0.0, PublicKeyToken=3ed3cdf4fdda3400")]
#else
	[TypeConverter(typeof(Design.SignalToNoiseRatioConverter))]
#endif
	[Serializable()]
    [ImmutableObject(true)]
#endif
    public struct SignalToNoiseRatio : IComparable, IFormattable, IXmlSerializable
#if !PocketPC || DesignTime
, ISerializable
#endif
	{
		#region  Private Variables 
		int pValue;
		#endregion

		#region  Shared members 
		/// <summary>Represents a value signifying a signal which is completely obscured.</summary>
		/// <remarks>If a GPS satellite reports an SNR of zero, it is likely below the
		/// horizon or completely blocked by an obstacle such as a building or tree.</remarks>
		public static readonly SignalToNoiseRatio NoSignal = new SignalToNoiseRatio(0);
		/// <summary>Represents a value of zero.</summary>
		/// <remarks>If a GPS satellite reports an SNR of zero, it is likely below the
		/// horizon or completely blocked by an obstacle such as a building or tree.</remarks>
		public static readonly SignalToNoiseRatio Empty = new SignalToNoiseRatio(0);
		/// <summary>Represents a value signifying a signal which is partially obscured.</summary>
		/// <remarks>GPS satellite signals with an SNR of 25 are not likely to become
		/// involved in a fix.</remarks>
		public static readonly SignalToNoiseRatio HalfSignal = new SignalToNoiseRatio(25);
		/// <summary>Represents a value signifying a signal which is not being obscured.</summary>
		/// <remarks>GPS satellite signals with an SNR of 50 are in direct line-of-sight
		/// and certain to become involved in obtaining a fix on the current location.</remarks>
		public static readonly SignalToNoiseRatio FullSignal = new SignalToNoiseRatio(50);
		#endregion

		#region  Events 
		//        /// <summary>
		//        /// Raised when the Value property has changed.
		//        /// </summary>
		//        /// <remarks>This event is typically used to notify an application that a satellite's
		//        /// signal has become more or less obscured.</remarks>
		//		public event SignalToNoiseRatioEventHandler ValueChanged;
		#endregion

		#region Method Operators
		/// <summary>Returns the sum of the current instance with the specified value.</summary>
		public SignalToNoiseRatio Add(SignalToNoiseRatio value)
		{
			return new SignalToNoiseRatio(this.Value + value.Value);
		}

		/// <summary>Returns the current instance decreased by the specified value.</summary>
		public SignalToNoiseRatio Subtract(SignalToNoiseRatio value)
		{
			return new SignalToNoiseRatio(this.Value - value.Value);
		}

		/// <summary>Returns the product of the current instance with the specified value.</summary>
		public SignalToNoiseRatio Multiply(SignalToNoiseRatio value)
		{
			return new SignalToNoiseRatio(this.Value * value.Value);
		}

		/// <summary>Returns the current instance divided by the specified value.</summary>
		public SignalToNoiseRatio Divide(SignalToNoiseRatio value)
		{
			return new SignalToNoiseRatio(this.Value / value.Value);
		}

		/// <summary>Returns the current instance increased by one.</summary>
		public SignalToNoiseRatio Increment()
		{
			return new SignalToNoiseRatio(this.Value + 1);
		}

		/// <summary>Returns the current instance decreased by one.</summary>
		public SignalToNoiseRatio Decrement()
		{
			return new SignalToNoiseRatio(this.Value - 1);
		}

		/// <summary>Returns whether the current instance is smaller than the specified value.</summary>
		public bool IsLessThan(SignalToNoiseRatio value)
		{
			return CompareTo(value) < 0;
		}

		/// <summary>
		/// Returns whether the current instance is smaller than or equivalent to the
		/// specified value.
		/// </summary>
		public bool IsLessThanOrEqualTo(SignalToNoiseRatio value)
		{
			return CompareTo(value) < 0 || Equals(value);
		}

		/// <summary>Returns whether the current instance is larger than the specified value.</summary>
		public bool IsGreaterThan(SignalToNoiseRatio value)
		{
			return CompareTo(value) > 0;
		}

		/// <summary>
		/// Returns whether the current instance is larger than or equivalent to the
		/// specified value.
		/// </summary>
		public bool IsGreaterThanOrEqualTo(SignalToNoiseRatio value)
		{
			return CompareTo(value) > 0 || Equals(value);
		}
		#endregion

		#region Operators
		public static SignalToNoiseRatio operator +(SignalToNoiseRatio left, int right) 
		{
			return left.Add(right);
		}

		public static SignalToNoiseRatio operator ++(SignalToNoiseRatio left) 
		{
			return left.Increment();
		}

		public static SignalToNoiseRatio operator --(SignalToNoiseRatio left) 
		{
			return left.Decrement();
		}

		public static SignalToNoiseRatio operator -(SignalToNoiseRatio left, int right) 
		{
			return left.Subtract(right);
		}

		public static SignalToNoiseRatio operator *(SignalToNoiseRatio left, int right) 
		{
			return left.Multiply(right);
		}

		public static SignalToNoiseRatio operator /(SignalToNoiseRatio left, int right) 
		{
			return left.Divide(right);
		}

		public static bool operator <(SignalToNoiseRatio left, int right) 
		{
			return left.CompareTo(right) < 0;
		}

		public static bool operator <=(SignalToNoiseRatio left, int right) 
		{
			return left.CompareTo(right) < 0 || left.Equals(right);
		}

		public static bool operator ==(SignalToNoiseRatio left, int right) 
		{
			return left.Equals(right);
		}

		public static bool operator !=(SignalToNoiseRatio left, int right) 
		{
			return !(left == right);
		}

		public static bool operator >=(SignalToNoiseRatio left, int right) 
		{
			return left.CompareTo(right) > 0 || left.Equals(right);
		}
	
		public static bool operator >(SignalToNoiseRatio left, int right) 
		{
			return left.CompareTo(right) > 0;
		}
		#endregion

		#region Conversions

		static public explicit operator SignalToNoiseRatio(string value) 
		{
            return SignalToNoiseRatio.Parse(value, CultureInfo.CurrentCulture);
        }

		static public implicit operator SignalToNoiseRatio(int value) 
		{
			return new SignalToNoiseRatio(value);
		}

		static public implicit operator string(SignalToNoiseRatio value)
		{
			return value.ToString("g", CultureInfo.CurrentCulture);
		}

		static public implicit operator SignalToNoiseRatioRating(SignalToNoiseRatio value)
		{
			return value.Rating;
		}

		static public implicit operator int(SignalToNoiseRatio value)
		{
			return value.Value;
		}

		#endregion

		#region  Constructors

		/// <summary>Creates a new instance.</summary>
		public SignalToNoiseRatio(int value)
		{
			pValue = value;
		}

        /// <summary>Creates a new instance by parsing the specified string.</summary>
        public SignalToNoiseRatio(string value)
            : this(value, CultureInfo.CurrentCulture)
		{
		}

        /// <summary>Creates a new instance by parsing the specified string using a specific culture.</summary>
        public SignalToNoiseRatio(string value, CultureInfo culture)
        {
            // Remove the decibals "DB" 
            value = value.ToUpper(culture).Replace(" DB", "").Trim();
            // If there's a rating in there, get rid of it
            if (value.IndexOf("(") != -1)
                value = value.Substring(0, value.IndexOf("(") - 1).Trim();
            // The remaining text should be a number
			pValue = int.Parse(value, culture);
        }

#if !PocketPC || DesignTime
        /// <summary>
        /// Creates a new instance by de-serializing previously-serialized information.
        /// </summary>
        /// <param name="info">A <strong>SerializationInfo</strong> object containing serialized data.</param>
        /// <param name="context">A <strong>StreamingContext</strong> object describing the context of serialization.</param>
        /// <remarks>This constructor will de-serialize a previously-serialized object.</remarks>
        private SignalToNoiseRatio(SerializationInfo info, StreamingContext context)
        {
            pValue = info.GetInt32("Value");
        }
#endif

        #endregion

        #region  Private Members
        internal void SetValue(int value)
		{
			if (pValue == value)
				return;
			if (value < 0 || value > 100)
#if PocketPC
				throw new ArgumentOutOfRangeException("Signal-to-noise ratio must be between 0 and 100.");
#else
				throw new ArgumentOutOfRangeException("value", value, "Signal-to-noise ratio must be between 0 and 100.");
#endif
			else
				pValue = value;
		}
		#endregion

		#region  Public Members 

		public bool Equals(SignalToNoiseRatio value)
        {
#if !PocketPC || Framework20
            if (object.ReferenceEquals(value, null))
			{
				return false;
			}
#endif
			return this.Value == value.Value;
		}

		public bool Equals(SignalToNoiseRatioRating value)
		{
			return (this.Rating == value);
		}

        public override bool Equals(object obj)
        {
#if !PocketPC || Framework20
            if (object.ReferenceEquals(obj, null))
				return false;
#endif

            if (obj is SignalToNoiseRatio)
                return this.Equals((SignalToNoiseRatio)obj);
            else if (obj is int)
                return this.Equals((int)obj);
            else if (obj is SignalToNoiseRatioRating)
                return this.Equals((SignalToNoiseRatioRating)obj);
			else
				return false;
		}

		/// <summary>Returns a unique code for the current instance used in hash tables.</summary>
		public override int GetHashCode()
		{
			return Value.GetHashCode();
		}

		/// <summary>Indicates if the value is zero.</summary>
		public bool IsEmpty
		{
			get
			{
				return pValue == 0;
			}
		}

		/// <summary>Returns a subjective rating of the signal strength.</summary>
		/// <value>A value from the <strong>SignalToNoiseRatioRating</strong> enumeration.</value>
		/// <remarks>
		/// This property is frequently used to present an SNR reading in a readable format to the user.
		/// This property is updated automatically as new information is received from the GPS device.
		/// </remarks>
		/// <seealso cref="SignalToNoiseRatioRating">SignalToNoiseRatioRating Enumeration</seealso>
		public SignalToNoiseRatioRating Rating //'Implements ISignalToNoiseRatio.Rating
		{
			get
			{
				if (Value > 0)
				{
					if (Value <= 15)
					{
						return SignalToNoiseRatioRating.Poor;
					}
					else if (Value <= 30)
					{
						return SignalToNoiseRatioRating.Moderate;
					}
					else if (Value <= 40)
					{
						return SignalToNoiseRatioRating.Good;
					}
					else
					{
						return SignalToNoiseRatioRating.Excellent;
					}
				}
				else
				{
					return SignalToNoiseRatioRating.None;
				}
			}
		}

		public override string ToString()
		{
			return this.ToString("g", CultureInfo.CurrentCulture);
		}

		/// <overloads>Outputs the current instance as a formatted string.</overloads>
		public string ToString(string format)
		{
			return this.ToString(format, CultureInfo.CurrentCulture);
		}

		/// <summary>
		/// Outputs the current instance as a string using the specified format and
		/// culture.
		/// </summary>
		public string ToString(string format, CultureInfo culture)
		{
            // Use the default format if none was given
            if (format == null || format.Length == 0)
                format = "G";

            // IF the format is "G", use the default
            if(String.Compare(format, "G", true, CultureInfo.InvariantCulture) == 0)
				format = "0 dB (r)";

            // Convert the string to lower-case
            format = format.ToLower(CultureInfo.InvariantCulture);

            // Replace "r" with the rating
			if(format.IndexOf("r") != -1)
                format = format.Replace("r", Rating.ToString());

            // And return the formatted value
			return pValue.ToString(format, culture);
		}

		/// <summary>
		/// Returns a <strong>SignalToNoiseRatio</strong> object containing a random
		/// value.
		/// </summary>
		public static SignalToNoiseRatio Random()
		{
			return new SignalToNoiseRatio((int)(new Random().NextDouble() * 50.0));
		}

		public static SignalToNoiseRatio Parse(string value)
		{
			return new SignalToNoiseRatio(value, CultureInfo.CurrentCulture);
		}

		public static SignalToNoiseRatio Parse(string value, CultureInfo culture)
		{
            return new SignalToNoiseRatio(value, culture);
		}

		/// <summary>Returns the numeric value of the signal strength.</summary>
		/// <value>An <strong>Integer</strong> value between 0 and 50, where 0 represents no signal and fifty represents a signal at full strength.</value>
		/// <remarks>
		/// This property indicates the strength of a received satellite signal.  GPS 
		/// satellites always transmit signals at the same strength, but the signal is often
		/// obscured by the atmosphere, buildings and other obstacles such as trees.  A value
		/// must be at about 30 or greater for a satellite to be able to maintain a fix for long.
		/// In the <see cref="Satellite">Satellite</see> class, this property is updated automatically as new information is 
		/// received from the GPS device.
		/// </remarks>
		public int Value 
		{
			get
			{
				return pValue;
			}
		}
		#endregion

		#region IComparable Members

		/// <summary>Compares the current instance to the specified value.</summary>
		public int CompareTo(SignalToNoiseRatio value)
		{
			return pValue.CompareTo(value.Value);
		}

		int IComparable.CompareTo(object obj)
		{
            return CompareTo((SignalToNoiseRatio)obj);
		}
		#endregion

        #region IXmlSerializable Members

        System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
        {
            return null;
        }

        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            pValue = int.Parse(reader.ReadString(), CultureInfo.InvariantCulture);
            reader.ReadEndElement();
        }

        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            writer.WriteString(pValue.ToString(CultureInfo.InvariantCulture));
        }

		#endregion


#if !PocketPC || DesignTime
        #region ISerializable Members

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Value", pValue);
        }

        #endregion
#endif

        #region IFormattable Members

        string IFormattable.ToString(string format, IFormatProvider formatProvider)
        {
            return ToString(format, (CultureInfo)formatProvider);
        }

        #endregion
    }

	/// <summary>
	/// Facilitates events and asynchronous calls which use an
	/// <strong>SignalToNoiseRatio</strong> object as a parameter.
	/// </summary>
	/// <example>
	/// This example declares a custom event using this delegate.
	/// <code lang="VB">
	/// Dim MySignalToNoiseRatioEvent As SignalToNoiseRatioEventHandler
	/// </code>
	/// <code lang="C#">
	/// SignalToNoiseRatioEventHandler MySignalToNoiseRatioEvent;
	/// </code>
	/// </example>
	/// <param name="e">A <see cref="SignalToNoiseRatioEventArgs">SignalToNoiseRatioEventArgs</see> object containing a <see cref="SignalToNoiseRatio">SignalToNoiseRatio</see> object involved with the event.</param>
	/// <param name="sender">The object which initiated the call to the delegate.</param>
	public delegate void SignalToNoiseRatioEventHandler(object sender, SignalToNoiseRatioEventArgs e);

	public sealed class SignalToNoiseRatioEventArgs : EventArgs
	{

		private SignalToNoiseRatio oSignalToNoiseRatio;

		public SignalToNoiseRatioEventArgs(SignalToNoiseRatio signalToNoiseRatio)
		{
			oSignalToNoiseRatio = signalToNoiseRatio;
		}

		public SignalToNoiseRatio SignalToNoiseRatio //'Implements ISignalToNoiseRatioEventArgs.SignalToNoiseRatio
		{
			get
			{
				return oSignalToNoiseRatio;
			}
		}
	}
}